﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;

namespace Cannonball
{
    class FallingWords
    {
        private Random randomGenerator;

        private List<Word> words;

        private Word currentWord;

        private WordsDictionary wordGenerator;

        private bool[] isOccupied;

        private int playfieldHeight;

        private int score;

        private int level;

        private int lives;

        private int frame;

        private int killedWordsInLevel;

        private int missedLinesCount;

        private int totalKilledWords;

        private System.Media.SoundPlayer playShoot;

        private System.Media.SoundPlayer playMiss;

        private System.Media.SoundPlayer playLifeLoss;

        private System.Media.SoundPlayer playLevelUp;

        //vars for the bonuses
        private string[] bonuses = { "[boom]", "[x2]", "[lives++]", "[clock]" };

        private int bonusRowPosition;

        private int bonusColPosition;

        private string currentBonus;

        private Random bonusGenerator;

        private int bonusCount;

        private Random bonusPositionGen;

        private List<string> bonusesInPlay;

        private List<int[]> bonusesCoordinates;

        private int[] coordinates;

        private int bonusIndex;

        private string bonusToKill;

        private bool shootBonus;

        private string killedBonus;

        private bool isBomb = false;

        private bool isClock = false;

        private int initialBonusCount;

        //vars for the player
        private List<string> playerObject;

        private ConsoleColor playerColor;

        private int playerRowPosition;

        private int playerColPosition;

        private int wordCenter;

        private bool didShoot;

        private int bombsCount;

        private int clocksCount;

        private static void PrintStringOnPosition(int x, int y, string c, ConsoleColor color = ConsoleColor.White)
        {
            Console.SetCursorPosition(x, y);
            Console.ForegroundColor = color;
            Console.Write(c);
        }

        public FallingWords()
        {
            randomGenerator = new Random();

            words = new List<Word>();

            wordGenerator = new WordsDictionary();

            playfieldHeight = 30;

            score = 0;

            level = 1;

            lives = 5;

            frame = 0;

            bonusCount = 0;

            initialBonusCount = 0;

            bonusesInPlay = new List<string>();

            bonusesCoordinates = new List<int[]>();

            shootBonus = false;

            killedWordsInLevel = 0;

            missedLinesCount = 0;

            totalKilledWords = 0;

            playShoot = new System.Media.SoundPlayer(@"..\..\sounds\shoot.wav");
            playMiss = new System.Media.SoundPlayer(@"..\..\sounds\miss.wav");
            playLifeLoss = new System.Media.SoundPlayer(@"..\..\sounds\lifeLoss.wav");
            playLevelUp = new System.Media.SoundPlayer(@"..\..\sounds\levelUp.wav");

            bombsCount = 0;

            clocksCount = 0;
        }

        public void StartGame()
        {
            StartMenu();
        }

        private void Play()
        {
            AddNewWords();

            while (true)
            {
                Console.Clear();

                if (MoveWords())
                {
                    AddNewWords();
                }

                DrawPlayer(); //draw the player under the word he is shooting at

                Draw();

                if (bonusCount != 0)
                {
                    GenerateBonus();   //can make the program randomly execute the generation
                }

                DrawBonus();

                Shoot();

                if (IsGameOver())
                {
                    Console.Clear();
                    Scoreboard scoreSystemObj = new Scoreboard();
                    scoreSystemObj.GameOver(score);
                    return;
                }

                // draw info
                PrintStringOnPosition(0, playfieldHeight, new string('-', Console.WindowWidth), ConsoleColor.DarkYellow);
                PrintStringOnPosition(27, playfieldHeight + 1, string.Format("{0} kills left to next level", (level * 5 - killedWordsInLevel)), ConsoleColor.DarkYellow);
                PrintStringOnPosition(18, playfieldHeight + 4, string.Format("LIVES: {0}", lives), ConsoleColor.DarkYellow);
                PrintStringOnPosition(37, playfieldHeight + 4, string.Format("BOMBS: {0}", bombsCount), ConsoleColor.DarkYellow);
                PrintStringOnPosition(55, playfieldHeight + 4, string.Format("LEVEL: {0}", level), ConsoleColor.DarkYellow);
                PrintStringOnPosition(18, playfieldHeight + 6, string.Format("SCORE: {0}", score), ConsoleColor.DarkYellow);
                PrintStringOnPosition(37, playfieldHeight + 6, string.Format("CLOCKS: {0}", clocksCount), ConsoleColor.DarkYellow);
                PrintStringOnPosition(55, playfieldHeight + 6, string.Format("KILLS: {0}", totalKilledWords), ConsoleColor.DarkYellow);

                Thread.Sleep(100);
            }
        }

        private void StartMenu()
        {
            Scoreboard scoreSystem = new Scoreboard();
            Console.WriteLine();
            Console.ForegroundColor = ConsoleColor.Gray;
            Console.WriteLine(String.Format("{0," + 53 + "}", "F A L L I N G   W O R D S  "));
            int topOffSet = (Console.WindowHeight / 2) - 10;
            Console.SetCursorPosition(0, topOffSet);
            Console.ForegroundColor = ConsoleColor.DarkYellow;
            Console.WriteLine(String.Format("{0," + 52 + "}", "Please enter your choice: "));
            Console.WriteLine();
            Console.WriteLine(String.Format("{0," + 43 + "}", "1. PLAY THE GAME!"));
            Console.WriteLine();
            Console.WriteLine(String.Format("{0," + 44 + "}", "2. View the scores"));
            Console.WriteLine();
            Console.WriteLine(String.Format("{0," + 33 + "}", "3. EXIT"));
            Console.WriteLine();
            Console.ForegroundColor = ConsoleColor.Gray;
            Console.WriteLine(String.Format("{0," + 70 + "}", (new string('-', 60))));
            Console.ForegroundColor = ConsoleColor.DarkYellow;
            Console.WriteLine();
            Console.WriteLine(String.Format("{0," + 55 + "}", "Things that might come in handy:"));
            Console.WriteLine();
            Console.WriteLine(String.Format("{0," + 68 + "}", "[clock] - Click Space whenever you like to freeze time!"));
            Console.WriteLine();
            Console.WriteLine(String.Format("{0," + 66 + "}", "[boom] - Click Enter to clear some words from screen!"));
            Console.WriteLine();
            Console.WriteLine(String.Format("{0," + 49 + "}", "[lives++] - Gives you an extra life!"));
            Console.WriteLine();
            Console.WriteLine(String.Format("{0," + 54 + "}", "[x2] - Doubles your points! Wait, what!? "));

            while (true)
            {
                ConsoleKeyInfo pressedKey = Console.ReadKey(true);
                if (pressedKey.Key == ConsoleKey.D1 || pressedKey.Key == ConsoleKey.NumPad1)
                {
                    Play();
                }
                else if (pressedKey.Key == ConsoleKey.D2 || pressedKey.Key == ConsoleKey.NumPad2)
                {
                    Console.Clear();
                    scoreSystem.SortScores(@"..\..\scores.txt");
                    scoreSystem.PrintScore();
                }
                else if (pressedKey.Key == ConsoleKey.D3 || pressedKey.Key == ConsoleKey.NumPad3)
                {
                    Environment.Exit(0);
                }
            }
        }

        private bool IsGameOver()
        {
            return lives <= 0;
        }

        private bool MoveWords()
        {
            // Speed
            if (frame != Math.Max(17 - 2 * level, 10))
            {
                frame++;

                return false;
            }

            for (int i = 0; i < words.Count; i++)   //add the falling bonus here - so it will move
            {
                if (words[i].position.row + 1 < playfieldHeight)
                {
                    words[i].position.row++;
                }
                else
                {
                    playLifeLoss.Play();

                    if (lives > 0)
                    {
                        lives--;
                    }
                    
                    if (currentWord == words[i])
                    {
                        currentWord = null;
                    }
                    
                    words.RemoveAt(i);
                    i--;
                }
            }

            if (bonusesInPlay.Count != 0) //!!          //used is the same logic for moving the words in the playgroung, only
            {                                                               //it's rewritten for the bonuses
                for (int i = 0; i < bonusesInPlay.Count; i++)
                {
                    if (bonusesCoordinates[i][1] + 1 < playfieldHeight)   //!!
                    {
                        bonusesCoordinates[i][1] = bonusesCoordinates[i][1] + 1;
                    }
                    else
                    {
                        playLifeLoss.Play();

                        if (lives > 0)
                        {
                            lives--;
                        }
                        bonusesInPlay.RemoveAt(i);
                    }
                }
            }

            frame = 0;

            return true;
        }

        private void Draw()
        {
            foreach (Word word in words)
            {
                Console.SetCursorPosition(word.position.col, word.position.row);
                Console.ForegroundColor = word.color;
                Console.Write(word.str);
            }
        }

        private void AddNewWords()
        {
            isOccupied = new bool[Console.WindowWidth]; //deleted bool[] and turned isOccupied into global var

            int numberOfWords;

            do
            {
                numberOfWords = randomGenerator.Next(0, 2 + Math.Min(level, 4)) - 1;
                numberOfWords = numberOfWords < 0 ? 0 : numberOfWords;

                if (numberOfWords == 0)
                {
                    missedLinesCount++;
                }
                else
                {
                    missedLinesCount = 0;
                }
            } while (missedLinesCount > 2);

            for (int i = 0; i < numberOfWords; i++)
            {
                ConsoleColor newWordColor = ConsoleColor.Green;
                string currentWord = wordGenerator.GetRandomWord(1 + (int)Math.Sqrt(level), 2 + level);
                while (IsRepeated(currentWord))
                {
                    currentWord = wordGenerator.GetRandomWord(1 + (int)Math.Sqrt(level), 2 + level);
                }
                int position = randomGenerator.Next(1, Console.WindowWidth - currentWord.Length - 1);
                while (!isFreePosition(isOccupied, position, currentWord.Length))
                {
                    position = randomGenerator.Next(1, Console.WindowWidth - currentWord.Length - 1);
                }
                SetPositionsOccupied(isOccupied, position, currentWord.Length);
                Word w = new Word(new Position(0, position), currentWord, newWordColor);
                words.Add(w);
            }
        }

        private void SetPositionsOccupied(bool[] isOccuppied, int position, int wordLength)
        {
            for (int i = position - 1; i < position + wordLength + 1; i++)
            {
                isOccuppied[i] = true;
            }
        }

        private bool isFreePosition(bool[] isOccuppied, int position, int wordLength)
        {
            for (int i = position; i < position + wordLength; i++)
            {
                if (isOccuppied[i])
                {
                    return false;                }
            }
            return true;
        }

        //private void GameOver()
        //{
        //    Console.Clear();

        //    PrintStringOnPosition(0, playfieldHeight, new string('-', Console.WindowWidth), ConsoleColor.DarkYellow);
        //    PrintStringOnPosition(27, playfieldHeight + 1, string.Format("{0} kills left to next level", (level * 5 - killedWordsInLevel)), ConsoleColor.DarkYellow);
        //    PrintStringOnPosition(18, playfieldHeight + 4, string.Format("LIVES: {0}", lives), ConsoleColor.DarkYellow);
        //    PrintStringOnPosition(55, playfieldHeight + 4, string.Format("LEVEL: {0}", level), ConsoleColor.DarkYellow);
        //    PrintStringOnPosition(18, playfieldHeight + 6, string.Format("SCORE: {0}", score), ConsoleColor.DarkYellow);
        //    PrintStringOnPosition(55, playfieldHeight + 6, string.Format("KILLS: {0}", totalKilledWords), ConsoleColor.DarkYellow);
        //    PrintStringOnPosition(35, playfieldHeight / 2, "GAME OVER!", ConsoleColor.Red);
        //    PrintStringOnPosition(35, playfieldHeight / 2 + 2, string.Format("SCORE: {0}", score), ConsoleColor.Red);
        //}

        private bool IsRepeated(string newWordStr)
        {
            foreach (Word word in words)
            {
                if (word.str == newWordStr)
                {
                    return true;
                }
            }

            return false;
        }

        private void Shoot()
        {

            Console.SetCursorPosition(Console.BufferWidth - 1, Console.BufferHeight - 2);

            if (Console.KeyAvailable)
            {
                ConsoleKeyInfo userInput = Console.ReadKey();
                // To prevent key hold buffer overflow
                while (Console.KeyAvailable) Console.ReadKey();
                string checkInput = userInput.KeyChar.ToString().ToLower();

                if (userInput.KeyChar != '[' && userInput.KeyChar != ']' && userInput.KeyChar != '+' && userInput.KeyChar != '2' &&
                    ((byte)userInput.Key < 65 || (byte)userInput.Key > 90) && userInput.Key != ConsoleKey.Enter && userInput.Key != ConsoleKey.Spacebar)     //check the if statement!
                {
                    return;
                }

                
                if (userInput.Key == ConsoleKey.Enter)
                {
                    if (bombsCount > 0)
                    {
                        words.Clear();
                        Console.Clear();
                        currentWord = null;
                        bombsCount--;
                    }

                    return;
                }

                if (userInput.Key == ConsoleKey.Spacebar)
                {
                    if (clocksCount > 0)
                    {
                        frame -= 50;
                        clocksCount--;
                    }

                    return;
                }
                

                if (bonusesInPlay.Count != 0) //shooting at bonus - used is the same logic for killing words, only it's rewritten
                {                                                                       //for the bonuses
                    if (bonusToKill != null)
                    {
                        if (bonusToKill[0] == checkInput[0])
                        {
                            playShoot.Play();
                            bonusesInPlay[bonusIndex] = ShootAtBonuses(bonusToKill);
                            bonusToKill = bonusesInPlay[bonusIndex];

                            GraphicsUtility.DrawLine(Console.BufferWidth / 2, playfieldHeight - 3,
                                bonusesCoordinates[bonusIndex][0], bonusesCoordinates[bonusIndex][1]);

                            score += 10;
                            if (bonusIsDead(bonusToKill))
                            {
                                bonusesInPlay.Remove(bonusToKill); // remove current from list
                                bonusesCoordinates.Remove(bonusesCoordinates[0]);
                                bonusToKill = null; // destroy the refference

                                ActivateBonuses(killedBonus);

                                killedWordsInLevel++;
                                totalKilledWords++;
                                if (killedWordsInLevel == level * 5)
                                {
                                    LevelUp();
                                }

                                score += 20;
                            }
                        }
                        else playMiss.Play();
                    }
                    else
                    {
                        for (int i = 0; i < bonusesInPlay.Count; i++)
                        {
                            if (bonusesInPlay[i][0] == checkInput[0])
                            {
                                shootBonus = true;
                                score += 10;
                                playShoot.Play();
                                bonusToKill = bonusesInPlay[i];
                                killedBonus = bonusesInPlay[i];
                                Console.ForegroundColor = ConsoleColor.Yellow;
                                bonusesInPlay[i] = ShootAtBonuses(bonusToKill); //!
                                bonusToKill = bonusesInPlay[i];
                                bonusIndex = i; //might need to change the name -> using it as a local var elsewhere

                                GraphicsUtility.DrawLine(Console.BufferWidth / 2, playfieldHeight - 3,
                                bonusesCoordinates[bonusIndex][0], bonusesCoordinates[bonusIndex][1]); //check if it workS
                                break;
                            }
                            else playMiss.Play();
                        }
                    }
                }

                if (currentWord != null && !shootBonus)     //shooting at word
                {
                    if (currentWord[0] == checkInput[0])
                    {
                        playShoot.Play();
                        currentWord.Shoot();

                        GraphicsUtility.DrawLine(Console.BufferWidth / 2, playfieldHeight - 3,
                            currentWord.position.col, currentWord.position.row);

                        score += 10;
                        if (currentWord.IsDead())
                        {
                            words.Remove(currentWord); // remove current from list
                            currentWord = null; // destroy the refference

                            killedWordsInLevel++;
                            totalKilledWords++;
                            if (killedWordsInLevel == level * 5)
                            {
                                LevelUp();
                            }

                            score += 20;
                        }
                    }
                    else if (currentWord[0] != checkInput[0] || userInput.KeyChar == '[') playMiss.Play(); //!!!
                }
                else if (!shootBonus) // get new word
                {
                    for (int i = 0; i < words.Count; i++)
                    {
                        if (words[i][0] == checkInput[0])
                        {
                            score += 10;
                            playShoot.Play();
                            currentWord = words[i];
                            currentWord.color = ConsoleColor.Red; // mark current word
                            currentWord.Shoot(); // destroy first char

                            GraphicsUtility.DrawLine(Console.BufferWidth / 2, playfieldHeight - 3,
                            currentWord.position.col, currentWord.position.row);
                            break;
                        }
                        else playMiss.Play();
                    }
                }
            }
        }

        private string ShootAtBonuses(string bonusToKill)
        {
            bonusToKill = bonusToKill.Remove(0, 1);
            return bonusToKill;
        }

        private bool bonusIsDead(string bonusToKill)
        {
            bool didDie = false;
            if (bonusToKill == string.Empty)
            {
                didDie = true;
                shootBonus = false;
            }

            return didDie;
        }

        private void LevelUp()
        {
            playLevelUp.Play();
            level++;
            killedWordsInLevel = 0;
            LevelUpBonus();
            words.RemoveRange(0, words.Count);

            if (level % 2 == 0)
            {
                bonusCount = level / 2;
                initialBonusCount = bonusCount;
            }
            else
            {
                bonusCount = initialBonusCount;
            }
        }

        private void LevelUpBonus()
        {
            score += level * 10;
        }

        private void GenerateBonus()
        {
            bonusGenerator = new Random();

            while (bonusCount > 0)
            {
                int currentBonusIndex = bonusGenerator.Next(0, bonuses.Length);  //not sure if the generator works
                currentBonus = bonuses[currentBonusIndex];
                GetBonusCoordinates(out bonusRowPosition, out bonusColPosition);
                //Note: bonusRowPosition -> corresponds to the Y axes of the bonus,
                //while bonusColPosition -> corresponds to the X axes of the bonus
                while (CheckBonusAndWordCoordinates())
                {
                    GetBonusCoordinates(out bonusRowPosition, out bonusColPosition);
                }

                bonusesInPlay.Add(currentBonus);      //adding the bonuses we have in the game to the current list
                coordinates = new int[2];
                coordinates[0] = bonusColPosition;
                coordinates[1] = bonusRowPosition;
                bonusesCoordinates.Add(coordinates);    //keeping the X and Y of each bonus in List<int[]>

                bonusCount--;
            }
        }

        private void DrawBonus()
        {
            for (int i = 0; i < bonusesInPlay.Count; i++)
            {
                Console.SetCursorPosition(bonusesCoordinates[i][0], bonusesCoordinates[i][1]);//(bonusColPosition, bonusRowPosition); //(X, Y);
                if (shootBonus)
                {
                    Console.ForegroundColor = ConsoleColor.Yellow;
                }
                else
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                }
                Console.WriteLine(bonusesInPlay[i]);
            }
        }

        private void GetBonusCoordinates(out int bonusRow, out int bonusCol) // + checks if bonusCol is > bufferWidth
        {
            bonusPositionGen = new Random();

            while (true)
            {
                bonusCol = bonusPositionGen.Next(1, Console.BufferWidth - 1);

                if (bonusCol + currentBonus.Length < Console.BufferWidth)
                {
                    bonusRow = 0;
                    break;
                }
            }
        }

        private bool CheckBonusAndWordCoordinates()
        {
            bool doCollide = false;

            for (int i = bonusColPosition; i <= bonusColPosition + currentBonus.Length; i++) //
            {
                if (isOccupied[i] == true)          //iterate through isOccupied starting from the column where the bonus will
                {                                   //be printed; if the corresponding cell of isOccupied == true; then
                    doCollide = true;
                    break;                          //we'll have collision - the bonus will be printed over the word; this is not
                }                                   //allowed so we'll generate new position for the the column of the bonus
            }

            if (!doCollide)
            {
                for (int i = bonusColPosition; i < bonusColPosition + currentBonus.Length; i++)
                {
                    isOccupied[i] = true;               //this ensures that no word or other bonus will be printed over the current one
                }
            }

            return doCollide;
        }

        private void ActivateBonuses(string killedBonus)
        {
            if (killedBonus == "[clock]")
            {
                clocksCount++;
            }
            else if (killedBonus == "[boom]")
            {
                bombsCount++;
            }
            else if (killedBonus == "[lives++]")
            {
                lives++;
            }
            else if (killedBonus == "[x2]")
            {
                score *= 2;
            }
        }

        private void BuildObject() //Creating the player //, out string pSymbol, out ConsoleColor pColor, out int rowPosition, out int colPosition 
        {
            playerObject = new List<string>();
            playerObject.Add("*");
            playerObject.Add("***");
            playerColor = ConsoleColor.Cyan;
            playerRowPosition = Console.WindowHeight; //!!    //for some reason the values are switched - playerRowPosition = columns
            playerColPosition = (Console.WindowWidth / 2) - 12;      //playerColPosition = rows
        }

        private void DrawPlayer()
        {
            BuildObject();
            Console.ForegroundColor = playerColor;

            for (int i = 0; i < playerObject.Count; i++)
            {
                Console.SetCursorPosition(playerRowPosition - i, playerColPosition + i);
                Console.WriteLine(playerObject[i]);
            }
        }
    }
}